﻿using System;
using System.Diagnostics;
using System.IO;
using System.IO.IsolatedStorage;
using System.Linq;
using System.Threading;
using System.Windows;
using WP7BackupServicePhone.Service;

namespace WP7BackupServicePhone
{
	/// <summary>
	/// Class that exposes methods you can use to backup and restore your iso storage configuration
	/// </summary>
	public class BackupService
	{
		private readonly BackupServiceConfiguration _configuration;

		IsolatedStorageFileStream _isoStorTemp;

		private readonly AutoResetEvent _autoResetEvent = new AutoResetEvent(false);

		private readonly BackupServiceClient _wcfClient;

		public BackupService(BackupServiceConfiguration configuration)
		{
			_configuration = configuration;
			_wcfClient = new BackupServiceClient("BasicHttpBinding_IBackupService", _configuration.WcfService);
			_wcfClient.UploadBytesCompleted += UploadBytesCompleted;
			//_wcfClient.UploadStreamCompleted += _wcfClient_UploadStreamCompleted;
			_wcfClient.DownloadStreamCompleted += DownloadStreamCompleted;
		}

		public event EventHandler BackupCompleted;

		private void OnBackupCompleted(EventArgs e)
		{
			Deployment.Current.Dispatcher.BeginInvoke(() =>
			                                          	{
			                                          		EventHandler handler = BackupCompleted;
			                                          		if (handler != null) handler(this, e);
			                                          	});
		}

		public event EventHandler RestoreCompleted;

		private void OnRestoreCompleted(EventArgs e)
		{
			Deployment.Current.Dispatcher.BeginInvoke(() =>
			                                          	{
			                                          		EventHandler handler = RestoreCompleted;
			                                          		if (handler != null) handler(this, e);
			                                          	});
		}

		public event EventHandler OperationCompleted;

		private void OnOperationCompleted(EventArgs e)
		{
			Deployment.Current.Dispatcher.BeginInvoke(() =>
														{
															EventHandler handler = OperationCompleted;
															if (handler != null) handler(this, e);
														});
		}

		#region "EchoTest"

		public void EchoTest()
		{
			ThreadPool.QueueUserWorkItem(o =>
			{
				_wcfClient.EchoCompleted += WcfClientEchoCompleted;
				_wcfClient.EchoAsync("Test");
				_autoResetEvent.WaitOne();
				OnOperationCompleted(EventArgs.Empty);
			});
		}

		void WcfClientEchoCompleted(object sender, EchoCompletedEventArgs e)
		{
			Debug.WriteLine(e.Result);
			_autoResetEvent.Set();
		}

		#endregion

		#region "Restore"
		
		/// <summary>
		/// Performs the restore.
		/// this function simply overwrite any file if they have the same names, do not explicitly clear the iso storage
		/// </summary>
		public void PerformRestore()
		{
			_backupset = null;
			ThreadPool.QueueUserWorkItem(o =>
			{
				// first off, get a list of all the files and directories in the backup set
				_wcfClient.GetBackupSetInfoCompleted += WcfClientGetBackupSetInfoCompleted;
				_wcfClient.GetBackupSetInfoAsync(_configuration.Application, _configuration.BackupSet);
				_autoResetEvent.WaitOne();
				// create the directory structure
				Debug.WriteLine(_backupset);
				RestoreDirectoryStructure();
				// start processing each file in sequence
				RestoreFiles();
				// operation completed
				OnRestoreCompleted(EventArgs.Empty);
			});
		}

		private void RestoreFiles()
		{
			foreach (string file in _backupset.Files)
			{
				_wcfClient.DownloadStreamAsync(
					new FileDownloadInfo { Application = _configuration.Application, Set = _configuration.BackupSet, Name = file }, file);
				_autoResetEvent.WaitOne();
			}
		}

		void DownloadStreamCompleted(object sender, DownloadStreamCompletedEventArgs e)
		{
			if (e.Error == null)
			{
				// write the file
				string file = e.UserState.ToString();
				using (var store = IsolatedStorageFile.GetUserStoreForApplication())
				{
					using (Stream stream = new MemoryStream(e.Result))
					{
						using (IsolatedStorageFileStream outstream = new IsolatedStorageFileStream(file, FileMode.Create, store))
						{
							// read from the input stream in 4K chunks
							// and save to output stream
							const int bufferLen = 4096;
							byte[] buffer = new byte[bufferLen];
							int count;
							while ((count = stream.Read(buffer, 0, bufferLen)) > 0)
							{
								outstream.Write(buffer, 0, count);
							}
						}
					}
				}
			}
			_autoResetEvent.Set();
		}

		private void RestoreDirectoryStructure()
		{
			using (var store = IsolatedStorageFile.GetUserStoreForApplication())
				foreach (string folder in _backupset.Folders.Where(folder => !store.DirectoryExists(folder)))
				{
					store.CreateDirectory(folder);
				}
		}

		private BackupSet _backupset;

		void WcfClientGetBackupSetInfoCompleted(object sender, GetBackupSetInfoCompletedEventArgs e)
		{
			if (e.Error == null)
				_backupset = e.Result;
			_autoResetEvent.Set();
		}

		#endregion

		#region "Backup"

		/// <summary>
		/// this function backs up your entire iso storage (only the sections the application can have safe access to)
		/// </summary>
		public void PerformBackup()
		{
			ThreadPool.QueueUserWorkItem(o =>
											{
												// first-off prepare the backup set and wait for the server to say ok
												_wcfClient.InitBackupSetCompleted += InitBackupSetCompleted;
												_wcfClient.InitBackupSetAsync(_configuration.Application, _configuration.BackupSet);
												_autoResetEvent.WaitOne();

												// start exploring the IsoStorage for file structures to replicate
												using (var store = IsolatedStorageFile.GetUserStoreForApplication())
												{
													string path = string.Empty; // takes the root
													// process the files
													UploadFiles(store, path, true);
												}
												OnBackupCompleted(EventArgs.Empty);
											});
		}

		void InitBackupSetCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
		{
			_autoResetEvent.Set();
		}

		private void UploadFiles(IsolatedStorageFile store, string rootPath, bool resursive)
		{
			var files = store.GetFileNames(rootPath + "\\*");
			foreach (var file in files)
				UploadBytes(Path.Combine(rootPath, file));
			if (!resursive)
				return;
			// get directoryies
			var dirs = store.GetDirectoryNames(rootPath + "\\*");
			foreach (var dir in dirs)
			{
				string path = Path.Combine(rootPath, dir);
				UploadFiles(store, path, true);
			}
		}

		private void UploadBytes(string filename)
		{
			_isoStorTemp = new IsolatedStorageFileStream(filename, FileMode.Open, IsolatedStorageFile.GetUserStoreForApplication());
			FileUploadInfo fileUploadInfo = new FileUploadInfo();
			fileUploadInfo.Application = _configuration.Application;
			fileUploadInfo.Set = _configuration.BackupSet;
			fileUploadInfo.Name = filename;
			fileUploadInfo.Bytes = ReadAll(_isoStorTemp);
			_wcfClient.UploadBytesAsync(fileUploadInfo, filename);
			// stop our thread until we get notified by the callback 
			_autoResetEvent.WaitOne();
		}

		void UploadBytesCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
		{
			_isoStorTemp.Close();
			_autoResetEvent.Set();
		}

		private static byte[] ReadAll(Stream input)
		{
			byte[] buffer = new byte[16 * 1024]; // read in 16k chunks
			using (MemoryStream ms = new MemoryStream())
			{
				int read;
				while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
				{
					ms.Write(buffer, 0, read);
				}
				return ms.ToArray();
			}
		}

		// uploading a stream it does not seems to work well..may I need to play with the webclient
		//private void UploadStream(string filename)
		//{
		//    using (OperationContextScope scope = new OperationContextScope(_wcfClient.InnerChannel))
		//    {
		//        MessageHeader header = MessageHeader.CreateHeader("Application", "", _application);
		//        OperationContext.Current.OutgoingMessageHeaders.Add(header);
		//        MessageHeader header = MessageHeader.CreateHeader("Set", "", _application);
		//        OperationContext.Current.OutgoingMessageHeaders.Add(header);
		//        MessageHeader header = MessageHeader.CreateHeader("Name", "", _application);
		//        OperationContext.Current.OutgoingMessageHeaders.Add(header);
		//    }
		//    _isoStorTemp = new IsolatedStorageFileStream(filename, FileMode.Open, IsolatedStorageFile.GetUserStoreForApplication());
		//    FileUploadStream fu = new FileUploadStream();
		//    fu.Stream = _isoStorTemp;
		//    _wcfClient.UploadBytesAsync(fu, filename);
		//    // stop our thread until we get notified by the callback 
		//    _uploadAutoResetEvent.WaitOne();
		//}

		//void _wcfClient_UploadStreamCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
		//{
		//    _isoStorTemp.Close();
		//    _uploadAutoResetEvent.Set();
		//}

		#endregion
	}
}